package org.dreamwork.jasmine2.web.controls;

import org.dreamwork.jasmine2.configure.JasmineConfig;
import org.dreamwork.jasmine2.engine.HttpContext;
import org.dreamwork.jasmine2.engine.IBindable;
import org.dreamwork.jasmine2.engine.IServerSideControl;
import org.dreamwork.jasmine2.engine.ViewStateManager;
import org.dreamwork.jasmine2.events.EventException;
import org.dreamwork.jasmine2.events.IDataBindListener;
import org.dreamwork.jasmine2.events.IDisposeListener;
import org.dreamwork.jasmine2.events.IPrerenderListener;
import org.dreamwork.jasmine2.i18n.I18nUtil;
import org.dreamwork.jasmine2.template.ITemplateControl;
import org.dreamwork.jasmine2.util.StringConverter;
import org.dreamwork.jasmine2.web.IWebContainer;
import org.dreamwork.jasmine2.web.IWebControl;
import org.dreamwork.text.TextFormat;
import org.dreamwork.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: seth
 * Date: 2009-1-4
 * Time: 17:31:17
 */
public class WebControl implements IWebControl, IBindable, IServerSideControl, IDataBindListener, IPrerenderListener {
    protected Object dataSource;
    protected String dataMember;
    protected String valueField;
    protected String id, clientId, name;
    protected String htmlTagName = "span";
    protected CharSequence text;
    protected Page page;
    protected boolean inited;
    protected boolean runAtServer = false;
    protected boolean autoPostback = false;
    protected boolean viewStateEnabled = true;
    protected boolean editable = true;
    protected boolean visible = true;
    protected Locale locale = null;

    protected ViewStateManager stateManager = new ViewStateManager (); 

    protected HttpContext context;

    protected List<IDataBindListener> dataBindEvents =
            Collections.synchronizedList (new ArrayList<IDataBindListener> ());
    protected List<IPrerenderListener> preRenderEvents =
            Collections.synchronizedList (new ArrayList<IPrerenderListener> ());
    protected List<IDisposeListener> disposeEvents =
            Collections.synchronizedList (new ArrayList<IDisposeListener> ());
    protected List<String> ignoredAttributes =
            Collections.synchronizedList (new ArrayList<String> ());
    protected Properties attrs = new Properties ();
    protected Map<String, String> style = new HashMap<> ();
    protected Map<String, String> dynamicAttributes = new HashMap<> ();
    protected Map<String, Object> oldAttributes = new HashMap<> ();
    protected List<String> cssClasses = new ArrayList<> ();

    private static final Logger logger = LoggerFactory.getLogger (WebControl.class);

    public void setAttribute (String attrName, String attrValue) {
        if (attrValue == null) return;
        if ("style".equalsIgnoreCase (attrName)) {
            String[] parts = attrValue.split (";");
            for (String s : parts) {
                int pos = s.indexOf (':');
                if (pos > -1) {
                    String styleName = s.substring (0, pos);
                    String styleValue = s.substring (pos + 1);
                    setStyle (styleName, styleValue);
                }
            }
        } else if ("visible".equalsIgnoreCase (attrName)) {
            visible = Boolean.valueOf (attrValue);
        } else if ("valueField".equalsIgnoreCase (attrName)) {
            valueField = attrValue;
        } else if ("viewStateEnabled".equalsIgnoreCase (attrName)) {
            viewStateEnabled = Boolean.valueOf (attrValue);
        } else if ("clientId".equalsIgnoreCase (attrName)) {
            setClientId (attrValue);
/*
        } else if ("class".equalsIgnoreCase (attrName)) {
            attrs.setProperty (attrName.toLowerCase (), attrValue);
            setClass (attrValue);
*/
        } else
            attrs.setProperty (attrName.toLowerCase (), attrValue);
    }

    public String getAttribute (String attrName) {
        return attrs.getProperty (attrName.toLowerCase ());
    }

    public void setStyle (String styleName, String styleValue) {
        style.put (styleName, styleValue);
    }

    public String getStyle (String styleName) {
        return style.get (styleName);
    }

    public Iterator<String> getStyleNames () {
        return style.keySet ().iterator ();
    }

    public boolean isContainsStyle (String name) {
        return style.containsKey (name);
    }

    public void setDynamicAttribute (String attrName, String expression) {
        dynamicAttributes.put (attrName.toLowerCase (), expression);
    }

    public String getDynamicAttribute (String attrName) {
        return dynamicAttributes.get (attrName.toLowerCase ());
    }

    public void setHttpContext (HttpContext context) {
        this.context = context;
    }

    public HttpContext getHttpContext () {
        return context;
    }

    public String getId () {
        return id;
    }

    public void setClientId (String clientId) {
        this.clientId = clientId;
    }

    public String getClientId () {
        if (clientId == null) return id;
        return clientId;
    }

    public String getHtmlTagName () {
        return htmlTagName;
    }

    public void setHtmlTagName (String htmlTagName) {
        this.htmlTagName = htmlTagName;
    }

    public String getName () {
        return name == null ? getClientId () : name;
    }

    public void setName (String name) {
        this.name = name;
    }

    public void setRunAtServer (boolean runAtServer) {
        this.runAtServer = runAtServer;
    }

    public boolean isRunAtServer () {
        return runAtServer;
    }

    public boolean isAutoPostback () {
        return autoPostback;
    }

    public void setAutoPostback (boolean autoPostback) {
        this.autoPostback = autoPostback;
    }

    public void setId (String id) {
        this.id = id;
    }

    public Page getPage () {
        return page;
    }

    public Iterator getAttributeNames () {
        return attrs.keySet ().iterator ();
    }

    public boolean isContainsAttribute (String attrName) {
        return attrs.containsKey (attrName.toLowerCase ());
    }

    public Iterator getDynamicAttributeNames () {
        return dynamicAttributes.keySet ().iterator ();
    }

    public boolean isContainsDynamicAttribute (String attrName) {
        return dynamicAttributes.containsKey (attrName.toLowerCase ());
    }

    public void setInnerText (CharSequence text) {
        this.text = text;
    }

    public CharSequence getInnerText () {
        return text;
    }

    public void setInnerHTML (CharSequence html) {
        text = html;
    }

    public CharSequence getInnerHTML () {
        return text;
    }

    // life circle
    public void init () {
        addDataBindEvent (this);
        addPreRenderEvent (this);

        if (context == null) context = HttpContext.current ();
        if (page == null) page = context.getPage ();

        if (ITemplateControl.class.isAssignableFrom (getClass ()))
            ((ITemplateControl) this).applyTemplate ();
        inited = true;
    }

    protected boolean isInited () {
        return inited;
    }

    public void render (PrintWriter writer) throws IOException, EventException {
        if (!visible) return;
//        firePreRenderEvents ();
        beginTag (writer);
    }

    public IWebControl copy () {
        return this;
    }

    public void setPage (Page page) {
        this.page = page;
    }

    protected WebControl baseCopy () throws IllegalAccessException, InstantiationException {
        WebControl control = getClass ().newInstance ();
        control.init ();
        control.valueField = valueField;
        control.dataMember = dataMember;
        control.dataSource = dataSource;
        control.editable = editable;
        control.autoPostback = autoPostback;
        control.name = name;
        control.page = page;
        control.clientId = clientId;
        control.dataPostbakced = dataPostbakced;
        control.runAtServer = runAtServer;
        control.viewStateEnabled = viewStateEnabled;
        control.visible = visible;
        control.text = text;

        control.oldAttributes.putAll (oldAttributes);
        control.ignoredAttributes.addAll (ignoredAttributes);
        control.attrs.putAll (attrs);
        control.dynamicAttributes.putAll (this.dynamicAttributes);
        control.style.putAll (style);

        for (IPrerenderListener l : preRenderEvents) {
            if (l != this) control.addPreRenderEvent (l);
        }
        for (IDataBindListener l : dataBindEvents) {
            if (l != this) control.addDataBindEvent (l);
        }
        for (IDisposeListener l : disposeEvents) {
            if (l != this) control.addDisposeEvent (l);
        }
        return control;
    }

    protected void renderAttributes (Writer writer) throws IOException {
        ServletContext application = context.getApplication ();
        boolean i18n = application.getAttribute (JasmineConfig.JASMINE_I18N_HOLDER) != null;

        for (Object o : attrs.keySet ()) {
            String attrName = (String) o;
            String attrValue = attrs.getProperty (attrName);
            if (!StringUtil.isEmpty (attrValue)) {
                writer.write (' ');
                writer.write (attrName);
                writer.write ("=\"");
                if (i18n) {
                    attrValue = I18nUtil.eval (attrValue, this);
                    attrValue = I18nUtil.escapeI18n (attrValue);
                }
                writer.write (TextFormat.htmlAttributeEscape (attrValue));
                writer.write ("\"");
            }
        }

        StringBuilder buffer = new StringBuilder ();
        for (Iterator iter = getStyleNames (); iter.hasNext ();) {
            String name = (String) iter.next ();
            String value = getStyle (name);
            if (buffer.length () > 0) buffer.append (';');
            buffer.append (name).append (':').append (value);
        }
        if (buffer.length () > 0)
            writer.write (" style=\"" + buffer + "\"");        
    }

    public ViewStateManager getViewState () {
        return stateManager;
    }

    public boolean isViewStateEnabled () {
        return viewStateEnabled;
    }

    public void setViewStateEnabled (boolean viewStateEnabled) {
        this.viewStateEnabled = viewStateEnabled;
    }

    public void setDataSource (Object dataSource) {
        this.dataSource = dataSource;
    }

    public Object getDataSource () {
        return dataSource;
    }

    public void setDataMember (String dataMember) {
        this.dataMember = dataMember;
    }

    public void setValueField (String fieldName) {
        this.valueField = fieldName;
    }

    public String getValueField () {
        return valueField;
    }

    public void dataBind () throws Throwable {
        if (dataSource == null) {
            String expression = getDynamicAttribute ("datasource");
            if (expression != null) {
                try {
                    dataSource = page.eval (expression);
                } catch (Exception e) {
                    System.out.println (this.getId () + " has error");
                    throw new EventException (e);
                }
            }
        }        
        fireDataBindEvents ();       
    }

    public synchronized void addDataBindEvent (IDataBindListener listener) {
        dataBindEvents.add (listener);
    }

    public synchronized void removeDataBindEvent (IDataBindListener listener) {
        dataBindEvents.remove (listener);
    }

    public synchronized IDataBindListener[] getDataBindEvents () {
        List<IDataBindListener> list = new ArrayList<IDataBindListener> (dataBindEvents.size ());
        list.addAll (dataBindEvents);
        IDataBindListener[] ssl = new IDataBindListener[list.size ()];
        return list.toArray (ssl);
    }

    public synchronized void fireDataBindEvents () throws EventException {
        IDataBindListener[] ssls = getDataBindEvents ();
        for (IDataBindListener ssl : ssls) {
            ssl.onDataBind (this);
        }
    }

    public synchronized void addPreRenderEvent (IPrerenderListener listener) {
        preRenderEvents.add (listener);
    }

    public synchronized void removePreRenderEvent (IPrerenderListener listener) {
        preRenderEvents.remove (listener);
    }

    public synchronized IPrerenderListener[] getPreRenderEvents () {
        List<IPrerenderListener> list = new ArrayList<IPrerenderListener> (preRenderEvents.size ());
        list.addAll (preRenderEvents);
        IPrerenderListener[] ssl = new IPrerenderListener[list.size ()];
        return list.toArray (ssl);
    }

    public synchronized void firePreRenderEvents () throws EventException {
        IPrerenderListener[] ssls = getPreRenderEvents ();
        for (IPrerenderListener ssl : ssls) {
            ssl.onPrerender (this, context);
        }
    }

    public synchronized void addDisposeEvent (IDisposeListener listener) {
        disposeEvents.add (listener);
    }

    public synchronized void removeDisposeEvent (IDisposeListener listener) {
        disposeEvents.remove (listener);
    }

    public synchronized IDisposeListener[] getDisposeEvents () {
        List<IDisposeListener> list = new ArrayList<IDisposeListener> (disposeEvents.size ());
        list.addAll (disposeEvents);
        IDisposeListener[] ssl = new IDisposeListener[list.size ()];
        return list.toArray (ssl);
    }

    public synchronized void fireDisposeEvents () throws EventException {
        IDisposeListener[] ssls = getDisposeEvents ();
        for (IDisposeListener ssl : ssls) {
            ssl.onDispose (this);
        }
    }

    public void dispose () throws Throwable {
        fireDisposeEvents ();
    }

    protected boolean isIgnoredAttribute (String attributeName) {
        for (String name : ignoredAttributes) {
            if (name.equalsIgnoreCase (attributeName)) return true;
        }
        return false;
    }

    protected void beginTag (PrintWriter writer) throws IOException {
        if (htmlTagName == null || htmlTagName.length () == 0) return;
        writer.print ("<" + htmlTagName);

        attrs.remove ("id");
        attrs.remove ("name");

        String clientId = getClientId ();
        String componentName = StringConverter.isEmpty (name) ? clientId : name;

        writer.print (" id=\"" + getClientId () + "\" name=\"" + componentName + "\"");
//        if (!editable) writer.print (" disabled=\"disabled\"");
        renderAttributes (writer);
/*
        for (Iterator iter = attrs.keySet ().iterator (); iter.hasNext ();) {
            String attrName = (String) iter.next ();
            if (isIgnoredAttribute (attrName)) continue;
            String value = attrs.getProperty (attrName);
            writer.print (" " + attrName + "=\"" + value + "\"");
        }
*/
        writer.println ("/>");
    }

    public void onDataBind (Object sender) throws EventException {
        for (String key : dynamicAttributes.keySet ()) {
            if (attrs.contains (key)) continue;
            if ("dataSource".equalsIgnoreCase (key)) continue;
            if ("dataValueSource".equalsIgnoreCase (key)) continue;
            String expression = dynamicAttributes.get (key);
            try {
                Object value = page.eval (expression);
                if (value != null) {
                    if ("editable".equalsIgnoreCase (key)) {
                        if (value instanceof Boolean || value.getClass() == boolean.class)
                            editable = (Boolean) value;
                        else if (value instanceof CharSequence)
                            editable = Boolean.valueOf (value.toString ());
                    } else if ("visible".equalsIgnoreCase (key)) {
                        if (value instanceof Boolean || value.getClass() == boolean.class)
                            visible = (Boolean) value;
                        else if (value instanceof CharSequence)
                            visible = Boolean.valueOf (value.toString ());
                    } else
//                        attrs.setProperty (key, value.toString ());
                    setAttribute (key, value.toString ());
                }
            } catch (Exception ex) {
                // ignore
            }
        }
    }

    public void onPrerender (IWebControl sender, HttpContext context) throws EventException {
    }

    protected void loadViewState (Object savedState) throws IOException {
        if (getViewState () != null)
            getViewState ().loadViewState (savedState);
    }

    public boolean isEditable () {
        return editable;
    }

    public void setEditable (boolean editable) {
        this.editable = editable;
    }

    public boolean isVisible () {
        return visible;
    }

    public void setVisible (boolean visible) {
        this.visible = visible;
    }

    private boolean dataPostbakced = false;
    public boolean isDataPostbacked () {
        return dataPostbakced;
    }

    public void setDataPostbacked (boolean flag) {
        dataPostbakced = flag;
    }
    protected IWebContainer parent;

    public void setParent (IWebContainer parent) {
        this.parent = parent;
    }

    @Override
    public void setClass (String classes) {
        if (!StringUtil.isEmpty (classes)) {
            classes = classes.trim ();
            if (classes.contains (" ")) {
                String[] a = classes.split ("\\s+");
                for (String p : a) {
                    cssClasses.add (p.trim ());
                }
            }

            setAttribute ("class", classes);
        }
    }

    @Override
    public List<String> getCssClasses () {
        return cssClasses;
    }

    public IWebContainer getParent () {
        return parent;
    }

/*
    public Locale getLocale () {
        return locale;
    }

    public void setLocale (Locale locale) {
        this.locale = locale;
    }
*/
}